package com.craftworks.rtmp;

import com.craftworks.util.BigEndian;
import com.craftworks.util.LittleEndian;

import java.util.Arrays;

public class Chunk
{
    private int fmt;
    private int csid;
    private int headSize;
    private int bodySize;
    private int msgLength;
    private int msgTypeID;
    private long msgStreamID;
    private int timestamp;
    private long extendedTimestamp;
    private byte[] body;

//    +--------------+----------------+--------------------+--------------+
//    | Basic Header | Message Header | Extended Timestamp | Chunk Data   |
//    +--------------+----------------+--------------------+--------------+
//    |<------------------- Chunk Header ----------------->|

    public Chunk(byte[] bytes, int offset, int length)
    {
        int b0 = BigEndian.getUINT8(bytes, offset);
        fmt = (b0 >> 6) & 0b11;
        int lb = b0 & 0b111111;
        if (lb == 0)
        {
            headSize = 3;
            csid = BigEndian.getUINT8(bytes, offset + 1) + 64;
        } else if (lb == 1)
        {
            headSize = 2;
            csid = BigEndian.getUINT8(bytes, offset + 2) * 256 +
                   BigEndian.getUINT8(bytes, offset + 1) + 64;
        } else
        {
            headSize = 1;
            csid = lb;
        }

        offset += headSize;

        switch (fmt)
        {
            case 0:
                timestamp = BigEndian.getUINT24(bytes, offset);
                msgLength = BigEndian.getUINT24(bytes, offset + 3);
                msgTypeID = BigEndian.getUINT8(bytes, offset + 6);
                msgStreamID = LittleEndian.getUINT32(bytes, offset + 7);
                headSize += 11;
                offset += 11;
                break;
            case 1:
                timestamp = BigEndian.getUINT24(bytes, offset);
                msgLength = BigEndian.getUINT24(bytes, offset + 3);
                msgTypeID = BigEndian.getUINT8(bytes, offset + 6);
                msgStreamID = -1;
                headSize += 7;
                offset += 7;
                break;
            case 2:
                timestamp = BigEndian.getUINT24(bytes, offset);
                msgLength = -1;
                msgTypeID = -1;
                msgStreamID = -1;
                headSize += 3;
                offset += 3;
                break;
            default:
                timestamp = -1;
                msgLength = -1;
                msgTypeID = -1;
                msgStreamID = -1;
        }

        if (timestamp == 0xFFFFFF)
        {
            extendedTimestamp = BigEndian.getUINT32(bytes, offset);
            headSize += 4;
            offset += 4;
        }

        if (msgLength == -1)
            bodySize = Math.min(length - headSize, Rtmp.getChunkSize());
        else
            bodySize = Math.min(length - headSize, Math.min(msgLength, Rtmp.getChunkSize()));
        body = Arrays.copyOfRange(bytes, offset, offset + bodySize);
    }

    public int getFormat()
    {
        return fmt;
    }

    public int getChunkStreamID()
    {
        return csid;
    }

    public int getHeadSize()
    {
        return headSize;
    }

    public int getBodySize()
    {
        return bodySize;
    }

    public int getMessageLength()
    {
        return msgLength;
    }

    public int getMessageTypeID()
    {
        return msgTypeID;
    }

    public long getMessageStreamID()
    {
        return msgStreamID;
    }

    public int getTimestamp()
    {
        return timestamp;
    }

    public long getExtendedTimestamp()
    {
        return extendedTimestamp;
    }

    public byte[] getData()
    {
        return body;
    }

    public int getSize()
    {
        return headSize + bodySize;
    }

    @Override
    public String toString()
    {
        return "Chunk{" +
               "fmt=" + fmt +
               ", csid=" + csid +
               ", msgLength=" + msgLength +
               ", msgTypeID=" + msgTypeID +
               ", msgStreamID=" + msgStreamID +
               '}';
    }

    public static int format(byte[] bytes, int offset)
    {
        int b0 = BigEndian.getUINT8(bytes, offset);
        return  (b0 >> 6) & 0b11;
    }

    public static int messageTypeID(byte[] bytes, int offset)
    {
        int b0 = BigEndian.getUINT8(bytes, offset);
        int fmt = (b0 >> 6) & 0b11;
        int lb = b0 & 0b111111;
        if (lb == 0)
            offset += 3;
        else if (lb == 1)
            offset += 2;
        else
            offset += 1;

        switch (fmt)
        {
            case 0:
                return BigEndian.getUINT8(bytes, offset + 6);
            case 1:
                return BigEndian.getUINT24(bytes, offset + 3);
            default:
                return -1;
        }
    }
}
